interface Mode {
   /**键盘按键按下 */
   keydown: KeyboardEvent,
   /**键盘按键抬起 */
   keyup: KeyboardEvent,
   /**鼠标按键按下 */
   mousedown: MouseEvent,
   /**鼠标按键抬起 */
   mouseup: MouseEvent,
   /**鼠标按键移动 */
   mousemove: MouseEvent,
   /**鼠标中键滚动 */
   wheel: WheelEvent
}
type ConstraintMode = 'key' | 'mouse' | 'wheel'
interface ConditionFunction {
   (event: Event, ...age: any[]): any
}
interface Condition {
   [k: string]: boolean | ConditionFunction
}
interface Listener {
   (event: Event): any
}
interface InitKey {
   word: string,
   key?: string[]
}
interface InitKeySet {
   [x: string]: InitKey
}
interface CommandOption {
   key?: string,
   constraintMode?: ConstraintMode,
   preventDefault?: boolean,
   disableModify?: boolean
}
type DefaultBehavior = 'menu' | 'wheel' | 'Tab' | 'F*' | 'Ctrl+*' | 'Shift+*' | 'Alt+*' | 'Ctrl+Shift+*' | 'Ctrl+Alt+*' | 'Shift+Alt+*'

// 异常处理
const erreoMap = {
   '001': '未知的按键',
}
function CommandError(code: string, ...ages: any[]) {
   const s = erreoMap[code]
   return typeof s === 'string' ? s : s(ages)
}

//阻止浏览器默认行为
const preventDefaultReg: Set<RegExp> = new Set
function defaultBehavior(event: Event, keyCode: string) {
   for (const reg of preventDefaultReg) {
      if (reg.test(keyCode)) {
         return event.preventDefault()
      }
   }
}
/**
 * 阻止浏览器默认行为
 * @param {DefaultBehavior | string} ages 描述阻止按键的字符串列表，可多填
 */
function preventDefault(...ages: (DefaultBehavior | string | RegExp)[]): void {
   for (const v of ages) {
      switch (v) {
         case 'menu':
            document.addEventListener('contextmenu', event => {
               event.preventDefault()
            })
            break
         case 'wheel':
            document.addEventListener('wheel', event => {
               event.preventDefault()
            }, {
               passive: false
            })
            break
         case 'F*':
            let a = []
            for (let i = 1; i < 13; i++) {
               a.push(String.fromCharCode(111 + i))
            }
            preventDefaultReg.add(new RegExp('^(' + a.join('|') + ')$'))
            break
         default:
            if (v instanceof RegExp) {
               preventDefaultReg.add(v)
            } else {
               const s = v.split(config.shortcutKeySeparator)
                  .map(a => a === '*' ? '.+' : KEYMAP.wordToCode(a))
                  .join('')
               preventDefaultReg.add(new RegExp('^' + s + '$'))
            }
      }
   }
}

//事件与快捷键依赖的全局变量
/**事件载体 */
let carrier: Document | HTMLElement = document
/**快捷键字符串: Command对象集合*/
const commandMap: Map<string, Set<Command>> = new Map
/**command字符串: Command对象*/
const commandSet: Map<string, Command> = new Map
/**功能键映射 */
const FUNCTION_KEY_MAP: { [k: string]: string } = {
   ctrlKey: '\u0011',
   shiftKey: '\u0010',
   altKey: '\u0012'
}
/**屏蔽键列表 */
const SHIELD_KEY_LIST: string[] = [
   'ControlLeft',
   'ControlRight',
   'ShiftLeft',
   'ShiftRight',
   'AltLeft',
   'AltRight',
   'CapsLock',
   'NumLock',
   'NumpadDivide',
   'NumpadMultiply',
   'NumpadDecimal'
]
const MOUSEPRESS: string[] = []
const SEPARATOR = '#.[]:'
const KEYMAP = {
   wordToCodes: new Map,
   codeToWords: new Map,
   keyToCodes: new Map,
   length: 0,
   /**
    * 单词转键值码
    * @param {string} word 
    * @returns 
    */
   wordToCode(word: string) {
      const c = this.wordToCodes.get(word)
      if (!c) throw CommandError('001')
      return c
   },
   /**
    * 键值码转单词
    * @param {string} code 
    * @returns 
    */
   codeToWord(code: string) {
      return this.codeToWords.get(code)
   },
   /**
    * event.code转键值码
    * @param {string} key 
    * @returns 
    */
   keyToCode(key: string) {
      const r = this.keyToCodes.get(key)
      if (!r) return this.addCode({ word: key })
      return r
   },
   /**
    * event.code转单词
    * @param {string} key 
    * @returns 
    */
   keyToWord(key: string) {
      const r = this.keyToCodes.get(key)
      const word = key.slice(0, 2) === 'M.' ? 'Mouse.' + key.slice(2) : key
      if (!r) return this.addCode({ word })
      return this.codeToWord(r)
   },
   /**
    * 添加键映射
    * @param {string} word 
    * @param {string[]} key
    * @param {string} code 
    * @returns {string} 键值码
    */
   addCode({ word, key }: { word: string, key?: string[] }, code?: string) {
      if (!code) {
         code = String.fromCharCode(2000 + this.length)
         this.length++
      }
      this.wordToCodes.set(word, code)
      this.codeToWords.set(code, word)
      if (key) {
         for (const v of key) {
            this.keyToCodes.set(v, code)
         }
      } else {
         this.keyToCodes.set(word, code)
      }
      return code
   }
}
//初始化键值表
{
   const INIT_KEYMAP: InitKeySet = {
      '\u0008': { word: 'Backspace' },
      '\u0009': { word: 'Tab' },
      '\u000d': { word: 'Enter', key: ['Enter', 'NumpadEnter'] },
      '\u0010': { word: 'Shift' },
      '\u0011': { word: 'Ctrl' },
      '\u0012': { word: 'Alt' },
      '\u001b': { word: 'Esc', key: ['Escape'] },
      '\u0020': { word: 'Space' },
      '\u0021': { word: 'PageUp' },
      '\u0022': { word: 'PageDown ' },
      '\u0023': { word: 'End' },
      '\u0024': { word: 'Home' },
      '\u0025': { word: 'Left', key: ['ArrowLeft'] },
      '\u0026': { word: 'Up', key: ['ArrowUp'] },
      '\u0027': { word: 'Right', key: ['ArrowRight'] },
      '\u0028': { word: 'Down', key: ['ArrowDown'] },
      '\u006a': { word: 'NumpadMultiply' },
      '\u006b': { word: 'NumpadAdd' },
      '\u006d': { word: 'NumpadSubtract' },
      '\u006e': { word: 'NumpadDecimal' },
      '\u006f': { word: 'NumpadDivide' },
      '\u00ba': { word: ';', key: ['Semicolon'] },
      '\u00bb': { word: '=', key: ['Equal'] },
      '\u00bc': { word: ',', key: ['Comma'] },
      '\u00bd': { word: '-', key: ['Minus'] },
      '\u00be': { word: '.', key: ['Period'] },
      '\u00bf': { word: '/', key: ['Slash'] },
      '\u00c0': { word: '~', key: ['Backquote'] },
      '\u00db': { word: '[', key: ['BracketLeft '] },
      '\u00dc': { word: '\\', key: ['Backslash'] },
      '\u00dd': { word: ']', key: ['BracketRight'] },
      '\u00de': { word: '\'', key: ['Quote'] },
      '\u03e8': { word: 'LeftMouse', key: ['M.0'] },
      '\u03e9': { word: 'MiddleMouse', key: ['M.1'] },
      '\u03ea': { word: 'RightMouse', key: ['M.2'] },
      '\u03eb': { word: 'Mouse3', key: ['M.3'] },
      '\u03ec': { word: 'Mouse4', key: ['M.4'] },
      '\u07d0': { word: 'Wheel' }
   }
   for (const k in INIT_KEYMAP) {
      KEYMAP.addCode(INIT_KEYMAP[k], k)
   }
   for (let i = 0; i < 10; i++) {
      const s = i.toString()
      KEYMAP.addCode({ word: s, key: ['Digit' + s] }, String.fromCharCode(48 + i))
      KEYMAP.addCode({ word: 'num' + s, key: ['Numpad' + s] }, String.fromCharCode(96 + i))
   }
   for (let i = 1; i < 13; i++) {
      const s = i.toString()
      KEYMAP.addCode({ word: 'F' + s }, String.fromCharCode(111 + i))
   }
   const KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
   for (let i = 0; i < 26; i++) {
      const v = KEY[i]
      KEYMAP.addCode({ word: v, key: ['Key' + v] }, String.fromCharCode(65 + i))
   }
}

/**条件 */
const condition: Condition = {}
/**模式约束对象 */
const ConstraintModeMap = {
   'key': { mode: ['keydown', 'keyup'] },
   'mouse': { mode: ['mousedown', 'mouseup', 'mousemove'], key: /(LeftMouse|MiddleMouse|RightMouse|Mouse[1-9])$/ },
   'wheel': { mode: ['wheel'], key: /Wheel$/ }
}
/**addEventListener事件默认选项 */
const listenerOption = {
   capture: true,
   passive: false
}

const findKeyWordCache: Map<string, any> = new Map
const customKeyCode: { code: number, initKey: InitKey }[] = []

/**配置对象 */
const config = {
   /**快捷键字符串分隔符 */
   shortcutKeySeparator: '+',
   /**条件字符串分隔符 */
   conditionSeparator: '&',
   /**迭代查找的顶层元素 */
   topElement: document,
   /**
    * 导入自定义键值码
    * @param {string[]} data 配置项
    */
   importCustomKeyCode(data: { code: number, initKey: InitKey }[]) {
      for (const v of data) {
         KEYMAP.addCode(v.initKey, v.code.toString())
      }
   },
   exportCustomKeyCode() {
      return customKeyCode
   },
   customKeyCode(code: number, initKey: InitKey) {
      if (code < 5000 || code > 6000) throw '自定义键值码必须在5000-6000之间'
      const keyCode = String.fromCharCode(code)
      customKeyCode.push({ code, initKey })
      KEYMAP.addCode(initKey, keyCode)
   },
   /**Command默认缺省配置 */
   get CommandDefaultOption() {
      return CommandDefaultOption
   }
}
function callBack(d: any) {
   if (d.sep === '[') {
      d.word = d.word.split('=')
   }
}
/**
 * 迭代查找元素
 * @param {string} findKeyWord 查找关键词
 * @param {HTMLElement} element 源元素
 * @returns {HTMLElement | undefined} 返回查到到的第一个元素
 */
function findElement(findKeyWord: string, element: any): HTMLElement | undefined {
   let ele: any = element
   let data = findKeyWordCache.get(findKeyWord)
   if (!data) {
      data = analyseKeyWord(findKeyWord, SEPARATOR, callBack)
      findKeyWordCache.set(findKeyWord, data)
   }
   while (!config.topElement.isEqualNode(ele)) {
      if (matchedElement(data, ele))
         return ele
      ele = ele.parentNode
   }
}
/**
 * 解析查询字符串
 * @param {string} keyWord 查询字符串
 * @param {string} separator 分隔符
 * @param {Function} callBack 回调函数
 * @returns {Object[]} 解析结果
 */
function analyseKeyWord(keyWord: string, separator: string, callBack: Function) {
   const data: { sep: string | undefined, word: any }[] = []
   let word = '', sep
   for (let i = 0; i < keyWord.length; i++) {
      const e = keyWord[i], x = separator.indexOf(e)
      if (x === - 1) {
         word += e
      } else {
         if (word) {
            const d = { word, sep }
            callBack(d)
            data.push(d)
            word = ''
         }
         sep = separator[x]
      }
   }
   const d = { word, sep }
   callBack(d)
   data.push(d)
   return data
}
/**
 * 匹配解析结果
 * @param {Object[]} data 解析结果
 * @param {HTMLElement} element 元素
 * @returns {boolean}
 */
function matchedElement(data: any, element: HTMLElement): Boolean | undefined {
   for (const v of data) {
      switch (v.sep) {
         case undefined:
            if (element.tagName !== v.word.toUpperCase())
               return false;
            break
         case '#':
            if (element.id !== v.word)
               return false
            break
         case '.':
            if (!element.classList.contains(v.word))
               return false;
            break
         case '[':
            if (v.word.length === 1) {
               if (!element.hasAttribute(v.word[0]))
                  return false
            } else {
               if (element.getAttribute(v.word[0]) !== v.word[1])
                  return false
            }
            break
         case ':':
            if (v.word === 'header') {
               if (!/H[1-6]/.test(element.tagName))
                  return false
            } else {
               if (!element[v.word])
                  return false
            }
            break
      }
   }
   return true
}
/**
 * 获取快捷键字符串
 * @param {KeyboardEvent} event
 * @returns {string} 
 */
function getKeyCode(event: Event, key: string): string {
   let keystr = ''
   for (const k in FUNCTION_KEY_MAP) {
      if (event[k]) {
         keystr += FUNCTION_KEY_MAP[k]
      }
   }
   keystr += key
   return keystr
}
/**
 * 获取命令
 * @param {string} cmd 命令名
 * @returns {Command|undefined} 命令
 */
function getCommand(cmd: string): Command | undefined {
   return commandSet.get(cmd)
}
/**
 * 搜索按键命令
 * @param {string} key 按键字符串
 * @returns {Command[]} 命令数组（只读）
 */
function searchKey(key: string): Command[] {
   const code = key.split(config.shortcutKeySeparator).map(a => KEYMAP.wordToCode(a)).join('')
   const set = commandMap.get(code)
   if (!set) return []
   return [...set]
}
carrier.addEventListener('keydown', event => {
   if (!SHIELD_KEY_LIST.includes(event.code)) {
      const key = getKeyCode(event, String.fromCharCode(event.keyCode))
      const shortcutKey = commandMap.get(key)
      shortcutKey && shortcutKey.forEach(a => {
         a.trigger(event)
      })
      defaultBehavior(event, key)
   }
}, listenerOption)
carrier.addEventListener('keyup', event => {
   if (!SHIELD_KEY_LIST.includes(event.code)) {
      const shortcutKey = commandMap.get(getKeyCode(event, String.fromCharCode(event.keyCode)))
      shortcutKey && shortcutKey.forEach(a => {
         a.trigger(event, 'keyup')
      })
   }
}, listenerOption)
carrier.addEventListener('mousedown', event => {
   const key = KEYMAP.keyToCode('M.' + event.button)
   const shortcutKey = commandMap.get(getKeyCode(event, key))
   shortcutKey && shortcutKey.forEach(a => {
      a.trigger(event, 'mousedown')
   })
   MOUSEPRESS.push(key)
   condition[KEYMAP.codeToWord(key)] = true
}, listenerOption)
carrier.addEventListener('mouseup', (event) => {
   const key = KEYMAP.keyToCode('M.' + event.button)
   const shortcutKey = commandMap.get(getKeyCode(event, key))
   shortcutKey && shortcutKey.forEach(a => {
      a.trigger(event, 'mouseup')
   })
   for (let i = 0; i < MOUSEPRESS.length;) {
      if (MOUSEPRESS[i] === key) {
         MOUSEPRESS.splice(i, 1)
      } else {
         i++
      }
   }
   condition[KEYMAP.codeToWord(key)] = false
}, listenerOption)
carrier.addEventListener('mousemove', (event) => {
   let key: string = MOUSEPRESS[MOUSEPRESS.length - 1]
   if (!key) return
   const shortcutKey = commandMap.get(getKeyCode(event, key))
   shortcutKey && shortcutKey.forEach(a => {
      a.trigger(event, 'mousemove')
   })
}, listenerOption)
carrier.addEventListener('wheel', event => {
   const key = getKeyCode(event, KEYMAP.keyToCode('Wheel'))
   const shortcutKey = commandMap.get(key)
   shortcutKey && shortcutKey.forEach(a => {
      a.trigger(event, 'wheel')
   })
   defaultBehavior(event, key)
}, listenerOption)
condition['@'] = function (event: Event, word: string) {
   return findElement(word, event.target)
}

/**Command默认缺省配置 */
const CommandDefaultOption = {
   key: '',
   constraintMode: '',
   preventDefault: false,
   disableModify: false
}
/**
 * @callback Listener
 * @param {Event} event 
 * @returns {any}
 */
/**命令类 */
class Command {
   constructor(command: string, option: CommandOption = {}) {
      const { key, constraintMode, preventDefault, disableModify } = Object.assign({}, CommandDefaultOption, option)
      this.#command = command
      if (!disableModify) commandSet.set(command, this)
      if (constraintMode) {
         if (!ConstraintModeMap[constraintMode]) throw '没有该模式:' + constraintMode
         this.#constraintMode = constraintMode
      }
      if (preventDefault) this.#preventDefault = true
      key && this.bind(key)
   }
   /**事件侦听器列表 */
   #listenerList: { [x: string]: Map<symbol, Function> } = {
      keydown: new Map,
      keyup: new Map,
      mousedown: new Map,
      mouseup: new Map,
      mousemove: new Map,
      wheel: new Map
   };
   /**是否阻止默认事件 */
   #preventDefault: boolean = false
   /**条件列表 */
   #condition: Set<string | ConditionFunction> = new Set
   /**快捷键码 */
   #keyCode: string = ''
   /**命令名 */
   #command
   /**委托描述 */
   #entrust: string = ''
   #constraintMode: ConstraintMode | undefined
   /**快捷键码（只读） */
   get keyCode(): string {
      return this.#keyCode
   }
   /**
    * 设置快捷键
    * @param {string} key 快捷键
    */
   bind(key: string): this {
      if (this.#constraintMode) {
         switch (this.#constraintMode) {
            case 'key':
               if (ConstraintModeMap['mouse'].key.test(key) || ConstraintModeMap['wheel'].key.test(key)) {
                  throw '模式约束禁止的操作'
               }
               break
            case 'mouse':
               if (!ConstraintModeMap['mouse'].key.test(key)) {
                  throw '模式约束禁止的操作'
               }
               break
            case 'wheel':
               if (!ConstraintModeMap['wheel'].key.test(key)) {
                  throw '模式约束禁止的操作'
               }
               break
         }
      }
      const oldKey = this.#keyCode
      const keyCode = key.split(config.shortcutKeySeparator).map(a => KEYMAP.wordToCode(a)).join('')
      this.#keyCode = keyCode
      const keySet = commandMap.get(oldKey)
      if (keySet) {
         keySet.delete(this)
         if (!keySet.size) {
            commandMap.delete(oldKey)
         }
      }
      const newKeySet = commandMap.get(key)
      if (newKeySet) {
         newKeySet.add(this)
      } else {
         commandMap.set(keyCode, new Set([this]))
      }
      return this
   }
   /**
    * 事件委托
    * @param word 选择器字符串
    */
   entrust(word: string): void {
      this.#entrust = word
   }
   on(listener: Listener): this
   /**
    * 添加侦听器
    * @param {string} 模式
    * @param {Function} listener 事件侦听器
    */
   on(mode: keyof Mode, listener: Listener): this
   on(age1: any, age2?: any): this {

      let listener: Listener, mode: string
      if (age2) {
         mode = age1
         listener = age2
      } else {
         if (typeof age1 === 'object') {
            for (const v in age1) {
               this.on(v as keyof Mode, age1[v])
            }
            return this
         }
         listener = age1
         mode = 'keydown'
      }
      if (this.#constraintMode) {
         if (!ConstraintModeMap[this.#constraintMode].mode.includes(mode)) {
            throw '模式约束禁止的操作'
         }
      }
      const key: symbol = Symbol('key')
      if (this.#listenerList[mode] === undefined) throw '模式不存在:' + mode
      this.#listenerList[mode].set(key, listener)
      return this
   }
   /**
    * 移除指定侦听器
    * @param {Symbol} key 键名
    */
   del(key: symbol, mode: keyof Mode = 'keydown'): this {
      if (this.#listenerList[mode] === undefined) throw mode + ' 模式不存在'
      this.#listenerList[mode].delete(key)
      return this
   }
   adopt(e: Event): boolean {
      for (let ctn of this.#condition) {
         if (typeof ctn === 'function') {
            if (!ctn(e)) return false
         } else {
            let no, as: any[] = []
            if (ctn[0] === '!') {
               no = true
               ctn = ctn.slice(1)
            }
            const c = ctn.indexOf(':')
            if (c) {
               as = ctn.slice(c + 1).split(/, |,/)
               ctn = ctn.slice(0, c)
            }
            const cn = condition[ctn]
            if (typeof cn === 'function') {
               if (no) {
                  if (cn(e, ...as)) return false
               } else {
                  if (!cn(e, ...as)) return false
               }
            } else {
               if (no) {
                  if (cn) return false
               } else {
                  if (!cn) return false
               }
            }
         }
      }
      return true
   }
   /**
    * 执行事件
    * @param {boolean} [skipCondition] 为true则跳过条件判断直接执行
    * @returns {boolean} 为true表示触发成功
    */
   trigger(event: Event, mode: keyof Mode = 'keydown'): boolean {
      let ele: any = carrier
      if (this.#entrust) {
         ele = findElement(this.#entrust, event.target)
         if (!ele) {
            return false
         }
      }
      if (this.adopt(event)) {
         this.#listenerList[mode].forEach(listener => {
            listener.call(ele, event, condition)
         })
      }
      if (this.#preventDefault) {
         event.preventDefault()
      }
      return true
   }
   /** * 快捷键 */
   set key(v) {
      this.bind(v)
   }
   /** * 快捷键 */
   get key() {
      return this.#keyCode.split('').map(a => KEYMAP.codeToWord(a)).join(config.shortcutKeySeparator)
   }
   /**条件字符串 */
   getConditionArray(): (string | ConditionFunction)[] {
      return [...this.#condition]
   }
   /**条件 */
   get condition() {
      return [...this.#condition].join(config.conditionSeparator)
   }
   /**
    * 添加条件
    * @param {string} condition 条件字符串
    */
   addCondition(...condition: Array<string | ConditionFunction>): this {
      for (const v of condition) {
         this.#condition.add(v)
      }
      return this
   }
   /**
    * 删除条件
    * @param {string} condition 条件字符串
    */
   delCondition(...condition: string[]): this {
      for (const v of condition) {
         this.#condition.delete(v)
      }
      return this
   }
   /**
    * 是否存在指定条件
    * @param condition 条件字符串
    * @returns {boolean} 为true表示存在
    */
   hasCondition(condition: string): boolean {
      return this.#condition.has(condition)
   }
}
//导出模块
module.exports = {
   Command,
   getCommand,
   searchKey,
   preventDefault,
   condition,
   config,
}